#ifndef __SLOTHTTPD_FCGI_FCGI_HPP__
#define __SLOTHTTPD_FCGI_FCGI_HPP__

#include <slothttpd/types.hpp>

#include <cstdint>
#include <cstddef>

#include <limits>
#include <string>
#include <ostream>

#include <arpa/inet.h>

namespace slothttpd {
namespace fcgi {

namespace params {

const std::string content_length = "CONTENT_LENGTH";
const std::string content_type = "CONTENT_TYPE";
const std::string gateway_interface = "GATEWAY_INTERFACE";
const std::string query_string = "QUERY_STRING";
const std::string remote_addr = "REMOTE_ADDR";
const std::string remote_port = "REMOTE_PORT";
const std::string script_name = "SCRIPT_NAME";
const std::string server_name = "SERVER_NAME";
const std::string server_protocol = "SERVER_PROTOCOL";
const std::string server_port = "SERVER_PORT";
const std::string server_addr = "SERVER_ADDR";

// send this
const std::string request_method = "REQUEST_METHOD";
const std::string script_filename = "SCRIPT_FILENAME";

} // namespace params

/**
 * Number of bytes in a fcgi::header. Future versions of the protocol
 * will not reduce this number.
 */
const std::size_t header_len = 8; //sizeof(header)

/**
 * @brief control_record_len length of all control record (begin/end/abort request record
 */
const std::size_t control_record_len = header_len + 8;

/**
 * @brief keep_conn FCGI_KEEP_CONN
 */
const std::size_t keep_conn = 1;

/**
 * @brief min_id_value
 */
const std::uint16_t min_id_value = 1;

/**
 * Value for version component of fcgi::header
 */
const std::uint8_t version_1 = 1;

/**
 * @brief max_content_length
 */
const std::size_t max_content_length = std::numeric_limits<std::uint16_t>().max();

const std::uint16_t managment_record_id = 0;

namespace role {

const std::uint16_t responder = 1;
const std::uint16_t authorizer = 2;
const std::uint16_t filter = 3;

} // namespace role

namespace type {

/**
 * Values for 'type' component of fcgi::header
 */
const std::uint8_t begin_request        = 1;
const std::uint8_t abort_request        = 2;
const std::uint8_t end_request          = 3;
const std::uint8_t params               = 4;
const std::uint8_t stdin                = 5;
const std::uint8_t stdout               = 6;
const std::uint8_t stderr               = 7;
const std::uint8_t data                 = 8;
const std::uint8_t get_values           = 9;
const std::uint8_t get_values_result    = 10;
const std::uint8_t unknown_type         = 11;
const std::uint8_t maxtype              = unknown_type;

} // namespace type

/**
 * Values for protocolStatus component of FCGI_EndRequestBody
 */
namespace protocol_status {

const std::uint8_t request_complete  = 0;
const std::uint8_t cant_mpx_conn     = 1;
const std::uint8_t overloaded        = 2;
const std::uint8_t unknown_role      = 3;

}
/*
 * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records
 */
const std::string max_conns     = "FCGI_MAX_CONNS";
const std::string max_reqs      = "FCGI_MAX_REQS";
const std::string mpxs_conns    = "FCGI_MPXS_CONNS";

struct header
{
    std::uint8_t    version;
    std::uint8_t    type;
    std::uint8_t    requestIdB1;
    std::uint8_t    requestIdB0;
    std::uint8_t    contentLengthB1;
    std::uint8_t    contentLengthB0;
    std::uint8_t    paddingLength;
    std::uint8_t    reserved;
    //std::uint8_t  contentData[contentLength];
    //std::uint8_t  paddingData[paddingLength];
};

struct begin_request_body
{
    std::uint8_t    roleB1;
    std::uint8_t    roleB0;
    std::uint8_t    flags;
    std::uint8_t    reserved[5];
};

struct begin_request_record :
        public fcgi::header,
        public fcgi::begin_request_body
{
};

struct end_request_body
{
    std::uint8_t    appStatusB3;
    std::uint8_t    appStatusB2;
    std::uint8_t    appStatusB1;
    std::uint8_t    appStatusB0;
    std::uint8_t    protocolStatus;
    std::uint8_t    reserved[3];
};

struct end_request_record :
        public fcgi::header,
        public fcgi::end_request_body
{
};

struct unknown_type_body
{
    std::uint8_t    type;
    std::uint8_t    reserved[7];
};

struct unknown_type_record :
        public fcgi::header,
        public fcgi::unknown_type_body
{
};

class header_view
{
public:
    /**
     * @brief load_uint32 converts 4 bytes sequence into uint32_t
     * @param b - pointer to sequence of 4 bytes in network byte order
     * @return 4 byte value in host order
     */
    inline static std::uint32_t load_uint32(std::uint8_t *b)
    {
        return ((b[3] & 0x7f) << 24) + (b[2] << 16) + (b[1] << 8) + b[0];
    }
    
    /**
     * @brief store_uint32 converts 32 bit value into 4 byte sequence
     * @param value - value to convert in host byte order
     * @param bytes - where to put 4 byte sequence in network order
     */
    inline static void store_uint32(std::uint32_t value, std::uint8_t *bytes)
    {
        value = ::htonl(value) | 0x10000000UL;
        std::memcpy(bytes, &value, sizeof value);
    }

    fcgi::header *const h;
    
    inline header_view(char *p) :
        h(reinterpret_cast<fcgi::header*>(p))
    {
    }
    
    inline std::uint16_t request_id() const
    {
        return (h->requestIdB1 << 8) + h->requestIdB0;
    }
    
    inline void request_id(std::uint16_t id)
    {
        h->requestIdB0 = id;
        h->requestIdB1 = (id >> 8);
    }
    
    inline std::uint8_t type() const
    {
        return h->type;
    
    }
    
    inline void type(std::uint8_t t)
    {
        h->type = t;
    }
    
    inline std::uint16_t content_length() const
    {
        return (h->contentLengthB1 << 8) + h->contentLengthB0;
    }
    
    inline void content_length(std::uint16_t len)
    {
        h->contentLengthB0 = len;
        h->contentLengthB1 = (len >> 8);
    }
    
    inline std::uint8_t padding_length() const
    {
        return h->paddingLength;
    }
    
    inline void padding_length(std::uint8_t len)
    {
        h->paddingLength = len;
    }
    
    void init(std::uint8_t t, std::uint16_t id, std::uint16_t con_len, std::uint8_t pad_len = 0)
    {
        h->version = fcgi::version_1;
        h->reserved = 0;
        
        type(t);
        content_length(con_len);
        padding_length(pad_len);
        request_id(id);
    }
    
    inline char_range_t content()
    {
        auto p = reinterpret_cast<char*>(h) + fcgi::header_len;
        return char_range_t(p, p + content_length());
    }
    
    inline char *begin()
    {
        return reinterpret_cast<char*>(h);
    }
    
    inline char *end()
    {
        return content().end() + h->paddingLength;
    }
    
    inline std::size_t size()
    {
        return end() - begin();
    }
};


inline std::ostream &operator<<(std::ostream &os, const header_view &v)
{
    return os << "--- FCGI_Header ---\n"
              << "  version   : " << int(v.h->version) << '\n'
              << "  type      : " << int(v.type()) << '\n'
              << "  requestId : " << v.request_id() << '\n'
              << "  contentLe : " << v.content_length() << ", "
              << "  paddingLe : " << int(v.padding_length()) << '\n';
}
 
class begin_request_view :
        public header_view
{
private:
    inline fcgi::begin_request_record *record() const
    {
        return static_cast<fcgi::begin_request_record*>(h);
    }

public:
    inline begin_request_view(char *p) :
        header_view(p)
    {
    }
    
    inline std::uint16_t role() const
    {
        return (record()->roleB1 << 8) + record()->roleB0;
    }
    
    inline void role(std::uint16_t r)
    {
        record()->roleB0 = r;
        record()->roleB1 = (r >> 8);
    }
    
    inline std::uint8_t flags() const
    {
        return record()->flags;
    }
    
    inline void flags(std::uint8_t f)
    {
        record()->flags = f;
    }
    
    void init(std::uint16_t id, std::uint16_t r, std::uint8_t fl = 0, std::uint8_t pad_len = 0)
    {
        this->header_view::init(fcgi::type::begin_request, id, sizeof(begin_request_body), pad_len);
        std::memset(record()->begin_request_body::reserved, 0, sizeof record()->begin_request_body::reserved);
        role(r);
        flags(fl);
    }
};

class end_request_view :
        public header_view
{
private:
    inline fcgi::end_request_record *record() const
    {
        return static_cast<fcgi::end_request_record*>(h);
    }
    
public:
    inline end_request_view(char *p) :
        header_view(p)
    {
    }
    
    inline std::uint32_t app_status() const
    {
        return load_uint32(&record()->appStatusB3);
    }
    
    inline void app_status(std::uint32_t status)
    {
        store_uint32(status, &record()->appStatusB3);
    }
    
    inline std::uint8_t protocol_status() const
    {
        return record()->protocolStatus;
    }
    
    inline void protocol_status(std::uint8_t status)
    {
        record()->protocolStatus = status;
    }
};

}
}

#endif
